package sk.uniba.fmph.asdf;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.imageio.ImageIO;
import sk.uniba.fmph.asdf.db.FullName;
import sk.uniba.fmph.asdf.db.SQLQuery;
import sk.uniba.fmph.asdf.db.SQLQueryHandler;
import sk.uniba.fmph.asdf.db.SQLQueryHandler.MessageDatePair;
import sk.uniba.fmph.asdf.exceptions.DatabaseException;
import sk.uniba.fmph.asdf.exceptions.OfflineException;
import sk.uniba.fmph.asdf.exceptions.PermissionDeniedException;
import sk.uniba.fmph.asdf.tempt.TestTemplate;
import sk.uniba.fmph.asdf.test.Test;
import sk.uniba.fmph.asdf.test.TestResult;
import sk.uniba.fmph.asdf.test.TestType;

/**
 * Trieda umoznuje vytiahnut zaznamy o testoch ci jednotlivych oblastiach a
 * exportovat ich do .csv suborov. Taktiez je moznost exportoval schemu a
 * zaznamy databazy, ci databazove logy. V neposlednom rade umoznuje
 * serializovat objekty.
 *
 * @author Jakub Ga??
 * @author Marek Timura
 */
public class Exporter {

    /**
     * Zlozka pre ukladanie sablon
     */
    public static final String DIR_TEMPLATES = "templates/";

    /**
     * Zlozka pre ukladanie testov
     */
    public static final String DIR_TESTS = "tests/";

    /**
     * Zlozka pre ukladanie exportovanych suborov.
     */
    public static final String DIR_EXPORT = "export/";

	/**
	 * Staticka konstanta, ktora sa posiela ako parameter pri exporte vysledkov
	 * vsetkych oblasti testov
	 */
	private static final int ALL_FIELDS = -1;

	/** mysqldump prikaz pre exportovanie databazy */
	private static final String SSH_MYSQLDUMP_COMMAND =
			"mysqldump testy -h kempelen -u testy -R -pVinaliesInternationales2012";

	private static File file;
	private static FileWriter fileWriter;
	private static String lineSeparator = System.getProperty("line.separator");

    private static SimpleDateFormat timestampFormat = new SimpleDateFormat("ddMMMyy-HHmmss");

	/**
	 * Metoda vykona export schemy databazy ako aj obsiahnutych zaznamov do
	 * suboru.
	 *
	 * @param filename
	 *            Meno suboru (bez pripony), do ktoreho si uzivatel praje ulozit
	 *            data.
	 * @throws PermissionDeniedException
	 *             Ak uzivatel nema dostatocne opravnenia vykonat danu operaciu
	 * @throws JSchException
	 *             Ak sa nepodarilo pripojit sa na server.
	 * @throws IOException
	 *             Ak nastala chyba pocas ziskavania exportu.
	 */
	public static void exportDatabase()
			throws PermissionDeniedException, JSchException, IOException {
		if (!Session.getAccount().isAdmin()) {
			throw new PermissionDeniedException();
		}
        String filename = String.format("%s%s%s_%s.txt", Session.getUserDir(), DIR_EXPORT, "dbdump", getNowAsString());
		JSch jsch = new JSch();
		com.jcraft.jsch.Session session =
				jsch.getSession(Session.SSH_USERNAME, Session.SSH_HOST);
		session.setConfig("StrictHostKeyChecking", "no");
		session.setPassword(Session.SSH_PASSWORD);
		session.connect();
		Channel channel = session.openChannel("exec");
		ChannelExec channelExec = (ChannelExec) channel;
		channelExec.setPty(true);
		channelExec.setCommand(SSH_MYSQLDUMP_COMMAND);
		channel.setInputStream(null);
		InputStream is = channel.getInputStream();
		channel.connect();

		OutputStream out = new FileOutputStream(new File(filename));
		int read = 0;
		byte[] bytes = new byte[1024];

		while ((read = is.read(bytes)) != -1) {
			out.write(bytes, 0, read);
		}
		is.close();
		out.flush();
		out.close();

		channel.disconnect();
		session.disconnect();
	}

	/**
	 * Vykona export zaznamov databazy do prislusneho suboru.
	 *
	 * @param filename
	 *            Meno suboru, do ktoreho si pouzivatel praje ulozit data
	 * @throws PermissionDeniedException
	 *             Ak pouzivatel nema dostatocne opravnenia vykonat danu
	 *             operaciu.
	 * @throws DatabaseException
	 * @throws IOException
	 *             Ak zlyhala input/output operacia.
	 */
	public static void exportLogs()
			throws PermissionDeniedException, DatabaseException, IOException, OfflineException {
		if (!Session.getAccount().isAdmin()) {
			throw new PermissionDeniedException();
		}
        String filename = String.format("%s%s%s_%s.txt", Session.getUserDir(), DIR_EXPORT, "dblog", getNowAsString());
		// Ziskame udaje z databazy
		SQLQueryHandler queryHandler = new SQLQueryHandler();
		queryHandler.openConnection();
		@SuppressWarnings("unchecked")
		ArrayList<MessageDatePair> logs =
				(ArrayList<MessageDatePair>) queryHandler
						.getResultSet(SQLQuery.GET_LOGS);
		queryHandler.closeConnection();

		// Zapiseme udaje do suboru
		FileWriter fw = new FileWriter(filename);
		for (MessageDatePair log : logs) {
			fw.write(log.date + lineSeparator + log.message + lineSeparator);
		}
		fw.close();
	}

    /**
     * Metoda vrati momentalny cas v tvare _ddMMMyy-hhmmss napr. _28Jan12-214405
     * @return momentalny cas
     */
    private static String getNowAsString() {
        return "_" + timestampFormat.format(new Date());
    }

	/**
	 * Vykona serializaciu objektu do prislusneho suboru s priponou .ser.
	 *
	 * @param serializableEntity
	 *            objekt jednej z potried SerializableEntity, ktory metoda
	 *            serializuje
	 * @param filename
	 *            meno suboru (bez pripony), do ktoreho si uzivatel praje ulozit
	 *            data
	 * @throws IOException
	 *             ak zlyhala input/output operacia
	 */
	public static void serializeEntity(SerializableEntity serializableEntity,
			String filename, String subdir, boolean rewrite) throws IOException {
        File f = new File(Session.getUserDir() + subdir + filename + ".ser");
        if (!rewrite && f.exists()) {
            return;
        }
		FileOutputStream fileOutputStream =
				new FileOutputStream(f);
		ObjectOutputStream objectOutputStream =
				new ObjectOutputStream(fileOutputStream);
		objectOutputStream.writeObject(serializableEntity);
		objectOutputStream.close();

	}


    /**
     * Metoda ulozi urcenu serializovatelnu entitu do urceneho suboru v prislusnej zlozke.
     *
     * @param serializableEntity entita urcena na serializaciu
     * @param filename nazov suboru
     * @param subdir nazov zlozky
     * @throws IOException ak zlyhala input/output operacia
     */
    public static void serializeEntity(SerializableEntity serializableEntity,
			String filename, String subdir) throws IOException {
        serializeEntity(serializableEntity, filename, subdir, true);
	}

    /**
     * Metoda vracia zoznam vsetkych sablon testov.
     * @return zoznam sablon
     */
	public static ArrayList<TestTemplate> getTestTemplates() {
		ArrayList<TestTemplate> testTemplates = new ArrayList<>();
		for (Test test : Session.getTests()) {
			TestTemplate testTemplate = test.getTemplate();
			if (!testTemplates.contains(testTemplate)) {
				testTemplates.add(testTemplate);
			}
		}
		return testTemplates;
	}

	/**
	 * Vrati zoznam vysledkov testov pre vsetky oblasti pre prislusnu sablonu.
	 *
	 * @param template
	 *            sablona, pre ktoru sa vrati zoznam vysledkov testov
	 * @return zoznam vysledkov testov
	 */
	public static ArrayList<TestResult> getTestResults(TestTemplate template) {
		return getFieldResults(template, ALL_FIELDS);
	}

    /**
     * Metoda vracia hodnotenie testu pre konkretneho studenta a pre danu sablonu.
     * @param template sablona testu
     * @param student meno studenta
     * @return hodnotenie testu
     */
	public static TestResult getTestResult(TestTemplate template,
			FullName student) {
		ArrayList<TestResult> testResults = getTestResults(template);
		for (TestResult testResult : testResults) {
			if (testResult.getStudent().equals(student)) {
				return testResult;
			}
		}
		return null;
	}

	/**
	 * Vrati zoznam vysledkov testov pre konkretnu oblast pre prislusnu sablonu.
	 *
	 * @param template
	 *            sablona, pre ktoru sa vrati zoznam vysledkov testov
	 * @param fieldId
	 *            id oblasti
	 * @return zoznam vysledkov testov
	 */
	public static ArrayList<TestResult> getFieldResults(TestTemplate template,
			int fieldId) {
		ArrayList<TestResult> testResults = new ArrayList<>();
		for (Test test : Session.getTests()) {
			if (test.getTemplate().getId() == template.getId() && test.getType() != TestType.PRACTICE) {
				testResults.add(test.getTestResult(fieldId));
			}
		}
		return testResults;
	}

	/**
	 * Exportuje zoznam vysledkov testov do uvedeneho suboru pre vsetky oblasti
	 * prislusnej sablony.
	 *
	 * @param template
	 *            sablona, pre ktoru sa exportuje zoznam vysledkov testov
	 * @param filename
	 *            meno suboru (bez pripony), do ktoreho si uzivatel praje ulozit
	 *            data
	 * @throws IOException
	 *             ak zlyhala input/output operacia
	 */
	public static void exportTestResults(TestTemplate template)
			throws IOException {
		ArrayList<TestResult> testResults = getTestResults(template);
        String filename = String.format("%s%s%s%s.csv", Session.getUserDir(), DIR_EXPORT, template.getName(), getNowAsString());
		setUpFileWriter(filename);

		fileWriter.write("sep=;" + lineSeparator);
		if (!testResults.isEmpty()) {
			String head = "Hodnotitel;Student;";
			for (int i = 0; i < testResults.get(0).getEvaluation().size(); i++) {
				head += "uloha " + (i + 1) + ";";
			}
			head += "spolu" + lineSeparator;
			fileWriter.write(head);
		}
		for (TestResult testResult : testResults) {
			fileWriter.write(testResult.toCSV() + lineSeparator);
		}
		closeFileWriter();
	}

    /**
     * Metoda exportuje prislusne hodnotenie testu do suboru.
     * @param testResult hodnotenie testu
     * @param filename nazov suboru
     * @throws IOException ak zlyhala input/output operacia
     */
	public static void exportTestResult(TestResult testResult)
			throws IOException {
        String filename = String.format("%s%s%s-%s%s.csv", Session.getUserDir(), DIR_EXPORT, testResult.getEvaluator().toString(), testResult.getStudent().toString(), getNowAsString());
		setUpFileWriter(filename);
		fileWriter.write(testResult.toCSV());
		closeFileWriter();
	}

	/**
	 * Exportuje zoznam vysledkov testov do uvedeneho suboru pre konkretnu
	 * oblast prislusnej sablony.
	 *
	 * @param template
	 *            sablona, pre ktoru sa exportuje zoznam vysledkov testov
	 * @param fieldId
	 *            id oblasti
	 * @param filename
	 *            meno suboru (bez pripony), do ktoreho si uzivatel praje ulozit
	 *            data
	 * @throws IOException
	 *             ak zlyhala input/output operacia
	 */
	public static void exportFieldResults(TestTemplate template, int fieldId) throws IOException {
		ArrayList<TestResult> fieldResults = getTestResults(template);
		String filename = String.format("%s%s%s-%d%s.csv", Session.getUserDir(), DIR_EXPORT, template.getName(), fieldId, getNowAsString());
        setUpFileWriter(filename);
		fileWriter.write("sep=;" + lineSeparator);
		if (!fieldResults.isEmpty()) {
			String head =
					"Hodnotitel;Student;uloha " + (fieldId - 1) + lineSeparator;
			fileWriter.write(head);
		}
		int field = fieldId - 2;
		for (TestResult testResult : fieldResults) {
			if (field < 0 || field >= testResult.getEvaluation().size())
				continue;
			fileWriter.write(testResult.getEvaluator() + ";"
					+ testResult.getStudent() + ";"
					+ testResult.getEvaluation().get(field) + lineSeparator);
		}
		closeFileWriter();
	}

    /**
     * Metoda vytvori FileWriter pre prislusny subor.
     * @param filename nazov suboru
     * @throws IOException ak zlyhala input/output operacia
     */
	private static void setUpFileWriter(String filename) throws IOException {
		file = new File(filename);
		file.createNewFile();
		fileWriter = new FileWriter(file);
	}

    /**
     * Metoda zatvori FileWriter.
     * @throws IOException ak zlyhala input/output operacia
     */
	private static void closeFileWriter() throws IOException {
		fileWriter.flush();
		fileWriter.close();
	}

	/**
	 * Ulozi obrazok testu do suboru.
	 *
	 * @param test
	 *            Test, ktoreho obrazok sa ma ulozit.
	 * @throws IOException
	 *             Ak sa nepodarilo ulozit subor s obrazkom.
	 */
	public static void saveTestImage(final Test test) throws IOException {
		File directory =
				new File(Session.getAccount().getEmail() + "/" + Test.IMAGE_DIR);
		directory.mkdirs();

		File file =
				new File(Session.getAccount().getEmail() + "/" + Test.IMAGE_DIR
						+ "/" + test.toString() + "-"
						+ test.getEvaluatorAccount().getEmail()
						+ Test.IMAGE_EXT);

		ImageIO.write((RenderedImage) test.getImage(), "png", file);
	}
}
